# -*- coding: utf-8 -*-
import csv
# from PyQt5.QtGui import QMovie
import ctypes
import json
import os
import sys
import time
import webbrowser
import matplotlib
import numpy as np
import serial  # 导入串口模块
import serial.tools.list_ports
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtCore import QTimer, QTime
from PyQt5.QtWidgets import QMainWindow, QMessageBox, QApplication
from saleae import automation
# import My_GLTextItem
import pyqtgraph as pg
# import pyqtgraph.opengl as gl
from matplotlib import pyplot as plt
from data_processing import data_process_fun, input_to_PPMcode, PPMcode_to_output, data_process_for_r
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import re
from win10toast import ToastNotifier
import matplotlib.animation as animation
# tab2的波形窗口,leftButtonPan=False表示鼠标不能拖动，靠矩形窗放大
pg.setConfigOptions(leftButtonPan=False, antialias=True)
from background import *
import source_rc
# 确保使用的是QT5
matplotlib.use('Qt5Agg')
progname = os.path.basename(sys.argv[0])
progversion = "0.1"
# debug用
def get_time_stamp():
    ct = time.time()
    local_time = time.localtime(ct)
    data_head = time.strftime("%Y-%m-%d %H:%M:%S", local_time)
    data_secs = (ct - int(ct)) * 1000
    time_stamp = "%s.%03d" % (data_head, data_secs)
    print(time_stamp)
# 定义加载界面类
# class LoadWindow(QMainWindow, win_gif_ui.Ui_MainWindow):
#     def __init__(self, parent=None):
#         super(LoadWindow, self).__init__(parent)
#         # self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)
#         # self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
#         self.setupUi(self)
#         self.runtime = QTimer(self)
#         self.runtime.timeout.connect(self.close_gif)
#         self.initUI()
#     def initUI(self):
#         try:
#             self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
#             # pyqt5设置窗体透明控件不透明
#             self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
#             self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool)
#             # 加载gif动画
#             self.movie = QMovie(":callipeg_1.gif")
#             self.label.setAlignment(QtCore.Qt.AlignCenter)
#             self.label.setMovie(self.movie)
#             # 播放gif动画
#             self.movie.start()
#             self.runtime.start(4050)
#             # 设置窗体位置和尺寸
#             # self.center()
#         except Exception as e:
#             print(e)
#     def close_gif(self):
#         # print('5seconds')
#         self.runtime.stop()
#         self.close()
# 自定义QT线程类，目前只用于控制Logic2程序的capture
class MyThread_capture(QThread):
    # 自定义信号对象
    trigger = pyqtSignal(list)
    d_id = 'A3E22C8E845D2C7D'
    d_r = 2.0
    s_r = 200000
    e_p = './'
    Configuration = []
    def __int__(self):
        # 初始化函数
        super(MyThread_capture, self).__init__()
        self.Configuration = [self.d_id, self.d_r, self.s_r, self.e_p]
    def set_configuration(self, d_id, d_r, s_r, e_p):
        self.d_r = d_r
        self.d_id = d_id
        self.s_r = s_r
        self.e_p = e_p
        self.Configuration = [self.d_id, self.d_r, self.s_r, self.e_p]
    def run(self):
        # 重写线程执行的run函数
        with automation.Manager.connect(port=10430) as manager:
            device_configuration = automation.LogicDeviceConfiguration(
                enabled_digital_channels=[0, 1, 2, 3, 4, 5, 6, 7],
                digital_sample_rate=self.Configuration[2],
                # digital_threshold_volts=3.3,
            )
            capture_configuration = automation.CaptureConfiguration(
                capture_mode=automation.TimedCaptureMode(duration_seconds=self.Configuration[1])
            )
            with manager.start_capture(
                    device_id=self.Configuration[0],
                    device_configuration=device_configuration,
                    capture_configuration=capture_configuration) as capture:
                # Wait until the capture has finished
                capture.wait()
                # Export raw digital data to a CSV file
                capture.export_raw_data_csv(directory=self.Configuration[3], digital_channels=[0, 1, 2, 3, 4, 5, 6, 7])
# 自定义QT线程类，只用于调用MATLAB的程序
class MyThread_MATLAB(QThread):
    # 自定义信号对象
    trigger = pyqtSignal(int)
    MATLAB_id = 0
    def __int__(self):
        # 初始化函数
        super(MyThread_MATLAB, self).__init__()
    def run(self):
        # 重写线程执行的run函数
        if self.MATLAB_id == 1:
            # 运行Newton_r_Psi_for_Python.m
            try:
                try:
                    print('.m')
                    now_path = os.path.abspath('.')  # 获取当前工作目录路径
                    now_path = now_path.replace('\\', '/')
                    eng = matlab.engine.start_matlab()
                    TN = ToastNotifier()
                    TN.show_toast("提示", "开始调用MATLAB迭代计算r和Psi", icon_path='logo_48.ico', duration=5, threaded=True)
                    eng.cd(now_path, nargout=0)
                    eng.Newton_r_Psi_for_Python(nargout=0)
                    TN.show_toast("提示", "完成计算r和Psi, 于当前程序所在文件夹导出mat", icon_path='logo_48.ico', duration=5,
                                  threaded=True)
                except:
                    print('exe')
                    TN = ToastNotifier()
                    TN.show_toast("EXE提示", "尝试调用Newton_r_Psi_for_Python.exe", icon_path='logo_48.ico', duration=5,
                                  threaded=True)
                    now_path = os.path.abspath('.')  # 获取当前工作目录路径
                    now_path = now_path.replace('\\', '/') + '/Newton_r_Psi_for_Python.exe'
                    os.system(now_path)
                self.trigger.emit(self.MATLAB_id)
            except:
                TN = ToastNotifier()
                TN.show_toast("MATLAB调用错误", "请检查New_r_Psi_for_Python.m文件", icon_path='logo_48.ico', duration=5,
                              threaded=True)
        if self.MATLAB_id == 2:
            # 运行Newton_PDpos_for_Python
            try:
                try:
                    now_path = os.path.abspath('.')  # 获取当前工作目录路径
                    now_path = now_path.replace('\\', '/')
                    eng = matlab.engine.start_matlab()
                    eng.cd(now_path, nargout=0)
                    eng.Newton_PDpos_for_Python(nargout=0)
                    TN = ToastNotifier()
                    TN.show_toast("提示", "PD坐标估计完成", icon_path='logo_48.ico', duration=5, threaded=True)
                except:
                    TN = ToastNotifier()
                    TN.show_toast("EXE提示", "尝试调用Newton_PDpos_for_Python.exe", icon_path='logo_48.ico', duration=5,
                                  threaded=True)
                    now_path = os.path.abspath('.')  # 获取当前工作目录路径
                    now_path = now_path.replace('\\', '/') + '/Newton_PDpos_for_Python.exe'
                    os.system(now_path)
                self.trigger.emit(self.MATLAB_id)
            except:
                TN = ToastNotifier()
                TN.show_toast("MATLAB调用错误", "请检查Newton_PDpos_for_Python.m文件", icon_path='logo_48.ico', duration=5,
                              threaded=True)
# 继承自 matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg类
class MyMplCanvas(FigureCanvas):
    # 这既是一个widget类也是一个FigureCanvas类
    def __init__(self, parent=None, width=8, height=6, dpi=100):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.fig.set_facecolor('#373741')
        # 3D 图窗
        self.axes = self.fig.add_subplot(111, projection='3d')
        self.compute_initial_figure()
        FigureCanvas.__init__(self, self.fig)
        self.setParent(parent)
        FigureCanvas.setSizePolicy(self,
                                   QtWidgets.QSizePolicy.Expanding,
                                   QtWidgets.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)
    def compute_initial_figure(self):
        pass
# 自定义动态画布类
class MyDynamicMplCanvas(MyMplCanvas):
    def __init__(self, *args, **kwargs):
        MyMplCanvas.__init__(self, *args, **kwargs)
        # # 定义一个QTimer定时器
        # timer = QtCore.QTimer(self)
        #
        # # 每隔1s=1000ms发出一次timeout信号，用于刷新
        # timer.timeout.connect(self.update_figure)
        # timer.start(3000)
    def compute_initial_figure(self):
        # 此处的axes在父类中已定义为3D画布
        self.axes.plot([1, 1, 1, 1], [1, 1, 1, 1], 'r')
    # 刷新画布槽函数
    def update_figure(self):
        X = []  # LED的x坐标
        Y = []
        Z = []
        try:
            with open('LED_PD_position.csv', 'r', encoding='utf-8', errors="ignore") as f:
                reader = csv.reader(f)
                for index, rows in enumerate(reader):
                    # index从0开始
                    if (index >= 1) and (index <= 4):
                        # 第一列rows[0]是ID
                        X.append(float(rows[1]))
                        Y.append(float(rows[2]))
                        Z.append(float(rows[3]))
        except:
            X = [12.0, 13.0, 61.5, 61.5]
            Y = [17.0, 61.5, 16.0, 60.0]
            Z = [82.0, 82.0, 82.0, 82.0]
        # 可视化迭代过程
        # try:
        #     # 尝试画位置迭代的过程
        #     count = 0
        #     pos_record = [[], [], []]
        #     with open('PDpos_record.csv', 'r', encoding='utf-8', errors="ignore") as f:
        #         reader = csv.reader(f)
        #         for row in reader:
        #             count += 1
        #             pos_record[0].append(100*float(row[0]))
        #             pos_record[1].append(100*float(row[1]))
        #             pos_record[2].append(100*float(row[2]))
        #     ims = []
        #     fig = plt.figure(figsize=(10, 10), dpi=100)
        #     for i in range(count):
        #         plt.xlim(0, 80)
        #         plt.ylim(0, 80)
        #         plt.xlabel('x(cm)')
        #         plt.ylabel("y(cm)")
        #         plt.grid(True)
        #         im = plt.scatter(pos_record[0][i], pos_record[1][i], s=300).findobj()
        #         ims.append(im)
        #     ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000)
        #     ani.save("Location_record_of_PD_during_iteration.gif", writer='pillow')
        # except:
        #     pass
        try:
            with open('PDpos_result.csv', 'r', encoding='utf-8', errors="ignore") as f:
                reader = csv.reader(f)
                for index, rows in enumerate(reader):
                    if index == 0:
                        x_r = 100 * float(rows[0])
                        y_r = 100 * float(rows[1])
                        z_r = 100 * float(rows[2])
        except:
            x_r = 35.0
            y_r = 35.0
            z_r = 8.0
        self.axes.cla()
        self.axes.scatter(X, Y, Z, c='yellow', linewidths=5, marker='D')
        self.axes.scatter(x_r, y_r, z_r, c='red', linewidths=1, marker='x')
        # 连线LED和接收器
        for i in range(len(X)):
            self.axes.plot([X[i], x_r], [Y[i], y_r], [Z[i], z_r], color='orange', linestyle='--', alpha=0.7)
        # 固定坐标轴范围
        self.axes.set_xlim(0, 85)
        self.axes.set_ylim(0, 85)
        self.axes.set_zlim(-1, 85)
        self.axes.set_xlabel('x', fontsize=20, color='white')
        self.axes.set_ylabel('y', fontsize=20, color='white')
        self.axes.set_zlabel('z(cm)', fontsize=20, color='white')
        # 设置背景和线颜色
        # self.axes.patch.set_facecolor('slategray')
        # self.fig.patch.set_facecolor('slategray')
        self.axes.set_facecolor('#373741')
        self.fig.set_facecolor('#373741')
        self.axes.tick_params(axis='x', colors='#ffffff')
        self.axes.tick_params(axis='y', colors='#ffffff')
        self.axes.tick_params(axis='z', colors='#ffffff')
        self.axes.grid(color='r', linestyle='--', linewidth=1, alpha=0.3)
        # 改变背景透明度，fail
        # self.axes.patch.set_alpha(0.1)
        LED_label = ["LED1", "LED2", "LED3", "LED4"]
        for x, y, z, i in zip(X, Y, Z, LED_label):
            location_str = '{i} ({x:.2f}, {y:.2f}, {z:.2f})'.format(i=i, x=x, y=y, z=z)
            self.axes.text(x, y, z + 3.0, location_str)
        R_str = 'PD ({x:.2f}, {y:.2f}, {z:.2f})'.format(x=x_r, y=y_r, z=z_r)
        self.axes.text(x_r, y_r, z_r, R_str)
        # 画布刷新！！！
        self.fig.canvas.draw_idle()
        self.fig.canvas.flush_events()
class MyWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyWindow, self).__init__(parent)
        # self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)
        # self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.capture_thread = MyThread_capture()
        self.Flag = False
        self.setupUi(self)
        self.port_flag = 0
        self.Digit_or_Alpha = 0  # 用于判断输入是0101还是含字母/汉字
        self.PPM_flag = 0  # 用于判断是否对01序列进行PPM调制
        self.export_path = './'
        self.ser = serial.Serial()
        self.looptime = QTimer(self)
        self.timer_progressBar = QTimer(self)
        self.timer_progressBar.timeout.connect(self.change_progressBar)
        self.PB_times = 0  # 根据capture的记录时长计算progressBar的更新次数
        self.PB_i = 0  # progressBar已经更新的次数
        self.port_check()
        self.configuration_init_1()
        self.configuration_init_2()
        self.graphicsView.plot()
        self.tab_2_flag = False  # tab2画图，flag=true,点击tab2时重新画图
        self.C_or_P_Flag = 0  # 定位还是通信，定位Flag=1, 通信Flag=0
        self.record_idx = 1  # 记录当前的标定数据的序号，用以重命名标定csv文件
        self.PD_pos = [35, 35, 8]  # PD的坐标
        self.Matlab_thread = MyThread_MATLAB()  # MATLAB线程，通过更改其属性MATLAB_id来分别运行两个m文件
        # 继承自自定义UI中的widget_1，widget尺寸为 width*100 x height*100 pixel
        self.dc = MyDynamicMplCanvas(self.widget_tab3_3D, width=9.55, height=7.75, dpi=100)
        self.dc.update_figure()
        # # 获取显示器分辨率大小
        # self.screenRect = self.desktop.screenGeometry()
        # self.height = self.screenRect.height()
        # self.width = self.screenRect.width()
        # 发送数据
        self.pushButton_send.clicked.connect(self.data_send)
        # 处理数据
        self.pushButton_data_process.clicked.connect(self.data_processing)
        # 清除
        self.pushButton_clear_s.clicked.connect(self.clear_send)
        self.pushButton_clear_r.clicked.connect(self.clear_receive)
        # self.pushButton_max.clicked.connect(self.max_or_narrow)
        # 点击打开/关闭串口按钮
        self.pushButton_port.clicked.connect(self.port_open)
        # 选择导出文件的位置
        self.pushButton_export.clicked.connect(self.select_export_path)
        # 循环发送
        self.checkBox_loop.setChecked(False)
        self.checkBox_loop.setCheckable(False)
        self.checkBox_loop.stateChanged.connect(self.loop_send)
        # 切换到第二个tabwidget
        self.tabWidget.currentChanged.connect(self.tabwidget_2_plot)
        # 判断是定位还是通信，由此选择帧头帧尾的检测次数
        self.checkBox_take_turns.stateChanged.connect(self.changeC_or_P_Flag)
        # 标定模式
        self.checkBox_calibration.stateChanged.connect(self.calibration_mode)
        # 由标定数据计算并导出z_data_MR.txt
        self.pushButton_calculate_z_data_MR.clicked.connect(self.calculate_z_data_MR)
        # 调用MATLAB计算r和Psi
        self.pushButton_calculate_r_Psi.clicked.connect(self.calculate_r_Psi)
        # 接收由thread_MATLAB发出的MATLAB_id信号，分别执行两个刷新功能
        self.Matlab_thread.trigger.connect(self.refresh_r_Psi_PDpos)
        # 计算z_real
        self.pushButton_calculate_z_real.clicked.connect(self.calculate_z_real)
        # 迭代估计PD的位置，刷新3D
        self.pushButton_position.clicked.connect(self.position_estimate)
    # 窗口最大化和正常化 # 暂时改为置顶
    # def max_or_narrow(self):
    # if self.isMaximized():
    #     self.showNormal()
    # else:
    #     self.showMaximized()
    # 根据历史记录初始化软件配置信息
    def configuration_init_1(self):
        try:
            with open('Configuration_1.json', 'r', encoding='utf-8') as fr:
                c_d = json.load(fr)
                self.lineEdit_startsymbol.setText(c_d['Start_symbol'])
                self.lineEdit_endsymbol.setText(c_d['End_symbol'])
                self.lineEdit_device.setText(c_d['Device_ID'])
                self.lineEdit_duration.setText(str(c_d['Duration']))
                self.lineEdit_sample_rate.setText(str(c_d['Sample_rate']))
                self.lineEdit_export_path.setText(str(c_d['Export_path']))
                self.export_path = c_d['Export_path']
        except:
            pass
    def configuration_init_2(self):
        try:
            with open('Configuration_2.json', 'r', encoding='utf-8') as fr:
                c_d = json.load(fr)
                self.lineEdit_symbol_T.setText(str(c_d['Symbol_T']))
                self.lineEdit_threshold.setText(str(c_d['Threshold']))
        except:
            pass
    # 窗口随鼠标移动------------------------------------------
    def mousePressEvent(self, evt):
        # 确定两个点(鼠标第一次按下的点)
        if evt.button() == QtCore.Qt.LeftButton:  # 判断是否为左键执行
            self.Flag = True
            self.mouse_x = evt.globalX()
            self.mouse_y = evt.globalY()
            self.origin_x = self.x()
            self.origin_y = self.y()
            # print(self.size().width())
            # print(self.size().height())
    def mouseMoveEvent(self, evt):
        if self.Flag == True:
            move_x = evt.globalX() - self.mouse_x
            move_y = evt.globalY() - self.mouse_y
            dest_x = self.origin_x + move_x
            dest_y = self.origin_y + move_y
            self.move(dest_x, dest_y)
    def mouseReleaseEvent(self, QMouseEvent):
        self.Flag = False
    # ------------------------------------------------------
    # 清空文本编辑框
    def clear_send(self):
        self.plainTextEdit_send.clear()
    # 清空接收端文本
    def clear_receive(self):
        self.textBrowser.clear()
    # checkBox_take_turns改变时，判断是定位还是通信，改变C_or_P_FlagFlag
    def changeC_or_P_Flag(self):
        if self.checkBox_take_turns.isChecked():
            self.C_or_P_Flag = 1
        else:
            self.C_or_P_Flag = 0
    # 选择csv文件导出的位置
    def select_export_path(self):
        export_path = QtWidgets.QFileDialog.getExistingDirectory(self, "选择csv文件导出位置", '')
        self.export_path = export_path
        self.lineEdit_export_path.setText(export_path)
    # 循环发送
    def loop_send(self):
        if self.checkBox_loop.isChecked():
            # 输入数据为空时，不允许循环发送
            if self.plainTextEdit_send.toPlainText() != '':
                self.looptime.timeout.connect(self.data_send)
            # 获取循环周期
            try:
                loop_period = int(self.plainTextEdit_period.toPlainText())
            except:
                loop_period = 10
                self.plainTextEdit_period.setPlainText('10')
                try:
                    TN = ToastNotifier()
                    TN.show_toast("循环周期错误", "循环周期改为默认10ms", icon_path='logo_48.ico', duration=3, threaded=True)
                except:
                    QMessageBox.about(self, "Period Error", "循环周期改为默认10ms")
            self.looptime.start(loop_period)
            self.looptime.setSingleShot(False)
            self.port_open()
            self.data_send()
        else:
            self.looptime.stop()
            self.ser.close()
            # self.pushButton_port.isEnabled(True)
    # 更新进度条
    def change_progressBar(self):
        if self.PB_i < self.PB_times:
            self.PB_i = self.PB_i + 1
            self.progressBar.setValue(self.PB_i)
    # 串口检测
    def port_check(self):
        # 检测所有存在的串口，将信息存储在字典中
        self.Com_Dict = {}
        port_list = list(serial.tools.list_ports.comports())
        self.comboBox_port.clear()
        for port in port_list:
            self.Com_Dict["%s" % port[0]] = "%s" % port[1]
            self.comboBox_port.addItem(port[0])
        if len(self.Com_Dict) == 0:
            self.comboBox_port.setItemText(0, "None")
    # 打开/关闭串口（槽函数）
    def port_open(self):
        if self.port_flag == 0:
            # 打开串口
            self.port_flag = 1
            try:
                self.ser.port = self.comboBox_port.currentText()
                self.ser.baudrate = int(self.comboBox_baudrate.currentText())
                self.ser.bytesize = int(self.comboBox_data_bit.currentText())
                self.ser.stopbits = int(self.comboBox_stop_bit.currentText())
                # 校验位parity
                self.ser.parity = "N"
                try:
                    self.ser.open()
                    # if self.ser.is_open :
                    #     print("串口打开成功")
                except Exception:
                    try:
                        TN = ToastNotifier()
                        TN.show_toast("串口调用错误", "此串口不能被打开 :(", icon_path='logo_48.ico', duration=5, threaded=True)
                    except:
                        QMessageBox.critical(self, "Port Error", "此串口不能被打开！")
                    return None
            except (OSError, serial.SerialException):
                try:
                    TN = ToastNotifier()
                    TN.show_toast("串口调用错误", "串口异常 :(", icon_path='logo_48.ico', duration=5, threaded=True)
                except:
                    QMessageBox.critical(self, "Port Error", "串口异常")
            if self.ser.isOpen():
                self.pushButton_port.setText("关闭串口")
                self.checkBox_loop.setCheckable(True)
        elif self.port_flag == 1:
            # 关闭串口
            self.port_flag = 0
            try:
                self.ser.close()
                self.checkBox_loop.setChecked(False)
                self.pushButton_port.setText("打开串口")
                self.looptime.stop()
            except:
                pass
    # 发送-接收数据及显示
    def data_send(self):
        # 打开更新地址
        try:
            site = self.plainTextEdit_send.toPlainText()
            if site == "更新" or site == "update":
                webbrowser.open('https://luwei6.lanzoum.com/b037gvi0b')
        except:
            pass
        # 重置进度条
        self.progressBar.reset()
        # 获取用户输入的参数Configuration
        Configuration = {'Device_ID': 'A3E22C8E845D2C7D', 'Duration': 2.0, 'Sample_rate': 500000,
                         'Export_path': './', 'Start_symbol': '', 'End_symbol': '', 'Input_history': ''}
        #   采样率d_s_r
        if self.lineEdit_sample_rate.text() == '':
            Configuration['Sample_rate'] = 500000
            self.lineEdit_sample_rate.setText('500_000')
        else:
            try:
                Configuration['Sample_rate'] = int(self.lineEdit_sample_rate.text())
            except:
                Configuration['Sample_rate'] = 500000
                self.lineEdit_sample_rate.setText('500_000')
        #   记录时长d_r
        if self.lineEdit_duration.text() == '':
            Configuration['Duration'] = 2.0
            self.lineEdit_duration.setText('2.0')
        else:
            try:
                Configuration['Duration'] = float(self.lineEdit_duration.text())
            except:
                Configuration['Duration'] = 2.0
                self.lineEdit_duration.setText('2.0')
        #   更改progressBar的步骤个数Duration*100, 即10ms更新一次
        self.progressBar.setRange(0, int(Configuration['Duration'] * 100))
        self.PB_times = int(Configuration['Duration'] * 100)
        #   设备号d_i
        if self.lineEdit_device.text() == '':
            Configuration['Device_ID'] = 'A3E22C8E845D2C7D'
            self.lineEdit_device.setText('A3E22C8E845D2C7D')
        else:
            try:
                Configuration['Device_ID'] = self.lineEdit_device.text()
            except:
                try:
                    TN = ToastNotifier()
                    TN.show_toast("设备号错误", "请输入正确的设备号 :)", icon_path='logo_48.ico', duration=5, threaded=True)
                except:
                    QMessageBox.critical(self, "Device ID Error", "请输入正确的设备号")

                Configuration['Device_ID'] = 'A3E22C8E845D2C7D'
                self.lineEdit_device.setText('A3E22C8E845D2C7D')
        #   csv文件导出位置
        if self.export_path == './':
            self.lineEdit_export_path.setText('默认为目前程序所在文件夹')
        Configuration['Export_path'] = self.export_path
        # Configuration['Export_path'] = 'C:/Users/龙威/Desktop'
        with open('Configuration.json', 'w', encoding='utf-8') as fw:
            json.dump(Configuration, fw)
        # 启动capture线程
        if self.checkBox_capture.isChecked():
            self.capture_thread.set_configuration(Configuration['Device_ID'], Configuration['Duration'],
                                                  Configuration['Sample_rate'], Configuration['Export_path'])
            self.capture_thread.start()
            # 启动进度条
            self.timer_progressBar.start(10)
            # 在启动capture后0.5秒左右再写入串口
            time.sleep(0.5)
        # 串口数据
        if self.ser.isOpen():
            input_data = self.plainTextEdit_send.toPlainText()
            input_record = input_data  # 备份用户输入数据，用以显示在textBrowser
            if input_data == '谢龙威':
                self.textBrowser.append('干嘛!?')
            # 判断输入是否为空
            if input_data != "":
                # 非空字符串
                input_s = input_data
                if input_data.isdigit():
                    # 输入为纯数字
                    self.Digit_or_Alpha = 1
                else:
                    # 输入含字母或汉字
                    self.Digit_or_Alpha = 2
            else:
                input_s = 'SYSU520'
            Configuration['Input_history'] = input_s
            # # 从输入端获取信噪比snr，默认为30dB
            # if self.plainTextEdit_snr.toPlainText() == '':
            #     snr = 30
            #     self.plainTextEdit_snr.setPlainText("30 (默认,可更改)")
            # else:
            #     try:
            #         snr = int(self.plainTextEdit_snr.toPlainText())
            #     except:
            #         snr = 30
            #         self.plainTextEdit_snr.setPlainText("30 (默认,可更改)")
            #         pass

            # input_encode = input_s
            input_list = ['AAA', 'BBB', 'CCC', 'DDD']  # 记录定位时轮发的四组内容，以换行符分割
            input_encode_list = ['', '', '', '']  # 记录PPM编码内容，如果定位，只有input_encode_list[0]有效
            input_encode_list[0] = input_s
            # 判断是否需要PPM
            if self.checkBox.isChecked():
                self.PPM_flag = 1  # 表示要PPM
                # 将输入数据转为PPM(str)
                if self.C_or_P_Flag == 0:
                    # 通信，不分割输入内容
                    input_encode_list[0] = input_to_PPMcode(input_s)
                else:
                    # 定位，根据换行符切割成四组
                    input_split = re.split('\n', input_s, 3)
                    for i in range(len(input_split)):
                        input_list[i] = input_split[i]
            else:
                try:
                    self.PPM_flag = 0  # 未输入，表示不要PPM
                    input_encode_list[0] = input_s
                    if self.C_or_P_Flag == 1:
                        # 定位，根据换行符切割成四组
                        input_split = re.split('\n', input_s, 3)
                        for i in range(len(input_split)):
                            input_list[i] = input_split[i]
                except:
                    pass
            # # 加控制某个LED的起始符@a09和终止符$ 单LED的时候
            # if self.lineEdit_startsymbol.text() == "":
            #     start_symbol = '@a09'
            #     self.lineEdit_startsymbol.setText('@a09')
            # else:
            #     start_symbol = self.lineEdit_startsymbol.text()
            #
            # if self.lineEdit_endsymbol.text() == "":
            #     end_symbol = '$'
            #     self.lineEdit_endsymbol.setText('$')
            # else:
            #     end_symbol = self.lineEdit_endsymbol.text()
            # Configuration['Start_symbol'] = start_symbol
            # Configuration['End_symbol'] = end_symbol
            # 判断是 1、单个LED 2、定位(四个LED轮流） 3、通信(四个LED同时)
            if self.checkBox_single.isChecked():  # 勾选，根据输入的起始符来写串口
                self.checkBox_take_turns.setChecked(False)
                if self.lineEdit_startsymbol.text() == "" or len(self.lineEdit_startsymbol.text()) > 4:
                    start_symbol = '@a09'
                    self.lineEdit_startsymbol.setText('@a09')
                else:
                    start_symbol = self.lineEdit_startsymbol.text()

                if self.lineEdit_endsymbol.text() == "":
                    end_symbol = '$'
                    self.lineEdit_endsymbol.setText('$')
                else:
                    end_symbol = self.lineEdit_endsymbol.text()
                data_input = start_symbol + input_encode_list[0] + end_symbol
                Configuration['Start_symbol'] = start_symbol
                Configuration['End_symbol'] = end_symbol
            else:
                if self.checkBox_take_turns.isChecked():  # 勾选，定位，四个LED轮流闪烁，内容只有barker+AAA,BBB,CCC,DDD+barker
                    self.checkBox_single.setChecked(False)
                    if self.PPM_flag == 1:
                        # 定位且PPM
                        A_encode = input_to_PPMcode(input_list[0])
                        B_encode = input_to_PPMcode(input_list[1])
                        C_encode = input_to_PPMcode(input_list[2])
                        D_encode = input_to_PPMcode(input_list[3])
                    else:
                        # 定位但不PPM
                        A_encode = input_list[0]
                        B_encode = input_list[1]
                        C_encode = input_list[2]
                        D_encode = input_list[3]
                    data_input = '@a05' + A_encode + '$' + '@a07' + B_encode + '$' + '@a08' + C_encode + '$' + '@a09' + \
                                 D_encode + '#'
                    self.lineEdit_startsymbol.setText('@a05 @a07 @a08 @a09')
                    Configuration['Start_symbol'] = '@a05 @a07 @a08 @a09'
                    Configuration['End_symbol'] = '#'
                else:
                    # 通信，四个灯同时
                    data_input = '@a15' + input_encode_list[0] + '&'
                    self.lineEdit_startsymbol.setText('@a15')
                    Configuration['Start_symbol'] = '@a15'
                    Configuration['End_symbol'] = '&'
            # UTF-8编码后写入串口
            try:
                # print('-->data_input', type(data_input), len(data_input), '\n', data_input)
                data_write = data_input.encode('utf-8')
                now = QTime.currentTime()
                num = self.ser.write(data_write)
                self.textBrowser.append("<font color='#f5f3f2'>" + "---{nowtime} -send  {length} bits :)".format(
                    nowtime=now.toString(), length=len(data_input)) + "<font>")
                self.textBrowser.append("<font color='#f5f3f2'>" + data_input + "<font>")
                self.textBrowser.append('')
            except:
                try:
                    TN = ToastNotifier()
                    TN.show_toast("串口写入错误", "Line 701 串口写入错误 :(", icon_path='logo_48.ico', duration=5, threaded=True)
                except:
                    QMessageBox.critical(self, "Send Error", "Line 701 串口写入错误")
        else:
            try:
                TN = ToastNotifier()
                TN.show_toast("串口调用错误", "串口都没打开！ O=('-'Q)  ", icon_path='logo_48.ico', duration=5, threaded=True)
            except:
                QMessageBox.critical(self, "Port Error", "串口都没打开！ O=('-'Q)  ")
        self.PB_i = 0
        # 保存用户配置历史Configuration_1
        with open('Configuration_1.json', 'w', encoding='utf-8') as fw:
            json.dump(Configuration, fw)
    # #### 发送数据end ##### #
    # ##### 数据处理begin ##### #
    def data_processing(self):
        now = QTime.currentTime()
        self.textBrowser.append(
            "<font color='#666670'>" + "---{nowtime} -start 开始处理数据 :)".format(
                nowtime=now.toString()) + "<font>")
        self.textBrowser.append('')
        QApplication.processEvents()
        Configuration_2 = {'Symbol_T': '', 'Threshold': 0.0, 'Idx_frame_header': 0, 'Idx_frame_end': 0}
        self.tab_2_flag = True  # 处理一次数据，self.tab_2_flag=true,防止数据没变的情况下反复画图卡顿
        if os.path.exists(self.export_path + '/digital.csv'):
            filename = self.export_path + '/digital.csv'
            times = []
            data = []
            with open(filename) as csvfile:
                csv_reader = csv.DictReader(csvfile)
                for row in csv_reader:
                    t = float(row['Time [s]'])
                    sum = 0
                    for i in range(8):
                        col_name = 'Channel ' + str(i)
                        sum = sum + (2 ** i) * int(row[col_name])
                    data.append(sum / 100.0)
                    times.append(t)
            T = float(self.lineEdit_symbol_T.text())
            Configuration_2['Symbol_T'] = T
            Ts = 1 / int(self.lineEdit_sample_rate.text())
            the_threshold = float(self.lineEdit_threshold.text())
            Configuration_2['Threshold'] = the_threshold
            output = data_process_fun(data, times, T, 1 / Ts, the_threshold, self.C_or_P_Flag)
            # 解码输出字符串
            try:
                now = QTime.currentTime()
                if self.C_or_P_Flag == 0:
                    # 通信，一帧数据
                    output_decode = PPMcode_to_output(output[0])
                    self.textBrowser.append("<font color='#e4d94c'>" + "---{nowtime} 通信 -decode :)".format(
                        nowtime=now.toString()) + "<font>")
                    self.textBrowser.append("<font color='#e4d94c'>" + "{output}".format(
                        output=output_decode) + "<font>")
                    self.textBrowser.append('')
                else:
                    # 定位，四帧数据
                    output_decode_1 = PPMcode_to_output(output[0])
                    output_decode_2 = PPMcode_to_output(output[1])
                    output_decode_3 = PPMcode_to_output(output[2])
                    output_decode_4 = PPMcode_to_output(output[3])
                    self.textBrowser.append(
                        "<font color='#e4d94c'>" + "---{nowtime} 定位 -decode :)".format(
                            nowtime=now.toString()) + "<font>")
                    self.textBrowser.append("<font color='#e4d94c'>" + "LED1->{output}".format(
                        output=output_decode_1) + "<font>")
                    self.textBrowser.append("<font color='#e4d94c'>" + "LED2->{output}".format(
                        output=output_decode_2) + "<font>")
                    self.textBrowser.append("<font color='#e4d94c'>" + "LED3->{output}".format(
                        output=output_decode_3) + "<font>")
                    self.textBrowser.append("<font color='#e4d94c'>" + "LED4->{output}".format(
                        output=output_decode_4) + "<font>")
                    self.textBrowser.append('')
            except:
                now = QTime.currentTime()
                self.textBrowser.append(
                    "<font color='#ff5370'>" + "---{nowtime} -decode 字符串解码失败 :(".format(
                        nowtime=now.toString()) + "<font>")
                self.textBrowser.append('')
            # 计算误码率, 仅通信时计算
            try:
                if os.path.exists('Configuration_1.json'):
                    with open('Configuration_1.json', 'r', encoding='utf-8') as fr:
                        c_d = json.load(fr)
                        user_input = c_d['Input_history']
                else:
                    user_input = self.plainTextEdit_send.toPlainText()
                    if user_input == '':
                        user_input = 'SYSU520'
                now = QTime.currentTime()
                self.textBrowser.append(
                    "<font color='#666670'>" + "---{nowtime} 误码率计算依据为:{user_input}...".format(
                        nowtime=now.toString(), user_input=user_input[0:6]) + "<font>")
                self.textBrowser.append('')
                input_list = list(input_to_PPMcode(user_input))[0:-13]  # 去掉帧尾
                num_error = 0
                if self.C_or_P_Flag == 0:
                    for i in range(min(len(input_list), len(output[0]))):
                        if int(input_list[i]) != output[0][i]:
                            num_error += 1
                    error_rate = num_error / min(len(input_list), len(output[0]))
                    self.label_biterrorrate.setText(str(error_rate))
                else:
                    self.label_biterrorrate.setText('仅通信时计算')
            except:
                self.label_biterrorrate.setText('error')
        # 保存用户配置历史Configuration_2
        with open('Configuration_2.json', 'w', encoding='utf-8') as fw:
            json.dump(Configuration_2, fw)
        # 标定模式下自动重命名
        if self.checkBox_calibration.isChecked():
            try:
                self.record_idx = int(self.lineEdit_record_idx.text())
            except:
                try:
                    TN = ToastNotifier()
                    TN.show_toast("获取序号错误", "请在\"处理\"按钮上方输入合法整数", icon_path='logo_48.ico', duration=5, threaded=True)
                except:
                    QMessageBox.critical(self, "Type Error", "获取序号错误")
                self.record_idx = 1
                self.lineEdit_record_idx.setText(str(self.record_idx))
            try:
                path = self.export_path
                files = [i for i in os.listdir(path)]  # os.listdir返回指定目录下的所有文件和目录名
                for file in files:
                    if file == 'digital.csv':
                        old_name = path + '/' + file  # 这个文件原来的名字
                        new = path + '/' + str(self.record_idx) + '.csv'  # 改为这个新名字
                        os.rename(old_name, new)
                        self.record_idx += 1
                        self.lineEdit_record_idx.setText(str(self.record_idx))
            except:
                pass
    # ##### 数据处理end ##### #
    # 第二个tabwidget画图槽函数
    def tabwidget_2_plot(self):
        if self.tabWidget.currentIndex() == 1:
            if self.tab_2_flag:
                self.tab_2_flag = False
                times = []
                data = []
                if os.path.exists(self.export_path + '/digital.csv'):
                    filename = self.export_path + '/digital.csv'
                    with open(filename) as csvfile:
                        csv_reader = csv.DictReader(csvfile)
                        for i, row in enumerate(csv_reader):
                            # 降低画图点数
                            if i % 10 == 0:
                                t = float(row['Time [s]'])
                                sum = 0
                                for j in range(8):
                                    col_name = 'Channel ' + str(j)
                                    sum = sum + (2 ** j) * int(row[col_name])
                                data.append(sum / 100.0)
                                times.append(t)
                # hour 和 temperature 分别是 : x, y 轴上的值
                self.graphicsView.plot(times,
                                       data,
                                       pen=pg.mkPen('#e4d94c', width=1)  # 线条颜色
                                       )
                labelStyle = {'color': '#fafafa', 'font-size': '14pt'}
                self.graphicsView.setLabel("left", "电压(V)", **labelStyle)
                self.graphicsView.setLabel("bottom", "时间(s)", **labelStyle)
                self.graphicsView.showGrid(x=True, y=True, alpha=0.5)  # 显示表格线
                self.graphicsView.setBackground('#373741')
    # 标定模式
    def calibration_mode(self):
        if self.checkBox_calibration.isChecked():
            self.checkBox.setChecked(True)
            self.checkBox_take_turns.setChecked(True)
            self.checkBox_capture.setChecked(True)
            self.lineEdit_duration.setText('4.5')
            folder = os.path.exists('./Calibration_data')
            # 判断是否存在Calibration_data文件夹,如果不存在则创建为文件夹
            if not folder:
                os.makedirs('./Calibration_data')  # makedirs 创建文件时如果路径不存在会创建这个路径
            now_path = os.path.abspath('.')  # 获取当前工作目录路径
            now_path = now_path.replace('\\', '/')
            self.lineEdit_export_path.setText(now_path + '/Calibration_data')
            self.export_path = now_path + '/Calibration_data'
            try:
                TN = ToastNotifier()
                TN.show_toast("提示", "标定模式请注意填写重命名序号 :)", icon_path='logo_48.ico', duration=2, threaded=True)
            except:
                QMessageBox.about(self, "Tips", "标定模式请注意填写重命名序号")
    # 标定完后遍历Calibration_data文件夹中的全部csv文件，计算得到z_data_MR.txt，传给MATLAB
    def calculate_z_data_MR(self):
        self.tabWidget.setCurrentIndex(0)
        now = QTime.currentTime()
        self.textBrowser.append(
            "<font color='#666670'>" + "---{nowtime} 开始计算z_data_MR并于当前程序所在文件夹导出txt".format(
                nowtime=now.toString()) + "<font>")
        QApplication.processEvents()
        now_path = os.path.abspath('.')  # 获取当前工作目录路径
        now_path = now_path.replace('\\', '/')
        path = now_path + '/Calibration_data'
        path_list = os.listdir(path)
        path_list.sort(key=lambda x: int(x.split('.')[0]))
        for filename in path_list:
            # 遍历csv文件
            if filename.endswith('.csv'):
                file_abs_name = path + '/' + filename
                self.textBrowser.append(
                    "<font color='#666670'>" + str(file_abs_name) + "<font>")
                QApplication.processEvents()
                times = []
                data = []
                with open(file_abs_name) as csvfile:
                    csv_reader = csv.DictReader(csvfile)
                    for row in csv_reader:
                        t = float(row['Time [s]'])
                        v_sum = 0
                        for i in range(8):
                            col_name = 'Channel ' + str(i)
                            v_sum = v_sum + (2 ** i) * int(row[col_name])
                        data.append(v_sum / 100.0)
                        times.append(t)
                symbol_T = float(self.lineEdit_symbol_T.text())
                sample_rate = int(self.lineEdit_sample_rate.text())
                threshold = float(self.lineEdit_threshold.text())
                C_or_P_Flag = 1  # C_or_P_Flag = 1表示数据处理为定位模式，8个互相关峰
                try:
                    z = data_process_for_r(data, times, symbol_T, sample_rate, threshold, C_or_P_Flag)
                    with open(now_path + "/z_data_MR.txt", "a") as f:
                        f.write('{0},{1},{2},{3}\n'.format(z[0], z[1], z[2], z[3]))
                except:
                    try:
                        TN = ToastNotifier()
                        TN.show_toast("计算错误", "计算z_data出错，请检查Tab 1中阈值、采样率、符号周期 :)", icon_path='logo_48.ico', duration=5,
                                      threaded=True)
                    except:
                        QMessageBox.critical(self, "z_data Error", "计算z_data出错，请检查Tab 1中阈值、采样率、符号周期")
        now = QTime.currentTime()
        self.textBrowser.append(
            "<font color='#666670'>" + "---{nowtime} 完成z_data_MR的计算".format(nowtime=now.toString()) + "<font>")
        self.textBrowser.append('')
    # 点击”计算r和Psi(m)“按钮，调用matlab运行
    def calculate_r_Psi(self):
        try:
            self.Matlab_thread.MATLAB_id = 1
            self.Matlab_thread.start()
            TN = ToastNotifier()
            TN.show_toast("提示", "开始调用MATLAB计算r和Psi", icon_path='logo_48.ico', duration=5, threaded=True)
        except:
            TN = ToastNotifier()
            TN.show_toast("调用错误", "请手动运行Newton_r_Psi_for_Python.m", icon_path='logo_48.ico', duration=5, threaded=True)
    # 由MATLAB线程触发，获取MATLAB迭代r和Psi结果并显示在tab3的两个下拉框中或更新tab3中的PD坐标
    def refresh_r_Psi_PDpos(self, MATLAB_id):
        if MATLAB_id == 1:
            # 更新r和Psi结果
            now_path = os.path.abspath('.')  # 获取当前工作目录路径
            now_path = now_path.replace('\\', '/')
            with open(now_path + '/r_Psi_result.csv') as csvfile:
                csv_reader = csv.DictReader(csvfile)
                for index, rows in enumerate(csv_reader):
                    # index从0开始,
                    if index == 0:
                        # rows是个字典dict，r是keys，Psi是values
                        rows_1 = list(rows.keys())
                        self.comboBox_r.setItemText(0, str(rows_1[0]))
                        self.comboBox_r.setItemText(1, str(rows_1[1]))
                        self.comboBox_r.setItemText(2, str(rows_1[2]))
                        self.comboBox_r.setItemText(3, str(rows_1[3]))
                        rows_2 = list(rows.values())
                        self.comboBox_Psi.setItemText(0, str(rows_2[0]))
                        self.comboBox_Psi.setItemText(1, str(rows_2[1]))
                        self.comboBox_Psi.setItemText(2, str(rows_2[2]))
                        self.comboBox_Psi.setItemText(3, str(rows_2[3]))
        if MATLAB_id == 2:
            # 更新PD的坐标并刷新3D画布
            try:
                now_path = os.path.abspath('.')  # 获取当前工作目录路径
                now_path = now_path.replace('\\', '/')
                with open(now_path + '/PDpos_result.csv') as csvfile:
                    csv_reader = csv.reader(csvfile)
                    for index, rows in enumerate(csv_reader):
                        if index == 0:
                            self.PD_pos = [100 * float(x) for x in rows]  # 转化为cm
                self.textBrowser.append("<font color='#e4d94c'>" + "坐标估计结果：({x}, {y}, {z})".format(
                    x=self.PD_pos[0], y=self.PD_pos[1], z=self.PD_pos[2]) + "<font>")
                self.textBrowser.append('')
                self.dc.update_figure()
                QApplication.processEvents()
                self.lineEdit_x.setText(str(self.PD_pos[0])[0:7])  # 显示小数点后6位
                self.lineEdit_y.setText(str(self.PD_pos[1])[0:7])
                self.lineEdit_z.setText(str(self.PD_pos[2])[0:7])
            except:
                try:
                    TN = ToastNotifier()
                    TN.show_toast("文件读取错误", "读取PDpos_result.csv失败，请检查文件 :)", icon_path='logo_48.ico', duration=5,
                                  threaded=True)
                except:
                    QMessageBox.critical(self, "Position Error", "读取PDpos_result.csv失败，请检查文件")
    # 计算用于实际定位的z_real，然后调用MATLAB得到PD位置估计结果
    def calculate_z_real(self):
        self.tabWidget.setCurrentIndex(0)
        QApplication.processEvents()
        z_real_path = os.path.abspath('.')  # 获取当前工作目录路径
        z_real_path = z_real_path.replace('\\', '/') + '/z_real.txt'
        if os.path.exists(z_real_path):
            os.remove(z_real_path)
            print('ok')
        digital_real_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "选择用于定位的csv文件", '', "Text Files (*.csv)")
        times = []
        data = []
        with open(digital_real_path) as csvfile:
            csv_reader = csv.DictReader(csvfile)
            for row in csv_reader:
                t = float(row['Time [s]'])
                sum = 0
                for i in range(8):
                    col_name = 'Channel ' + str(i)
                    sum = sum + (2 ** i) * int(row[col_name])
                data.append(sum / 100.0)
                times.append(t)
        T = float(self.lineEdit_symbol_T.text())
        sample_rate = int(self.lineEdit_sample_rate.text())
        the_threshold = float(self.lineEdit_threshold.text())
        try:
            now_path = os.path.abspath('.')  # 获取当前工作目录路径
            now_path = now_path.replace('\\', '/')
            now = QTime.currentTime()
            self.textBrowser.append(
                "<font color='#666670'>" + "---{nowtime} 计算z_real".format(nowtime=now.toString()) + "<font>")
            QApplication.processEvents()
            z = data_process_for_r(data, times, T, sample_rate, the_threshold, 1)
            with open(now_path + "/z_real.txt", "a") as f:
                f.write('{0},{1},{2},{3}\n'.format(z[0], z[1], z[2], z[3]))
                now = QTime.currentTime()
                self.textBrowser.append(
                    "<font color='#666670'>" + "---{nowtime} 完成导出z_real.txt".format(nowtime=now.toString()) + "<font>")
                QApplication.processEvents()
        except:
            try:
                TN = ToastNotifier()
                TN.show_toast("计算错误", "计算z_real出错，请检查Tab 1中阈值、采样率、符号周期 :)", icon_path='logo_48.ico', duration=5,
                              threaded=True)
            except:
                QMessageBox.critical(self, "z_real Error", "计算z_real出错，请检查Tab 1中阈值、采样率、符号周期")
    # 调用MATLAB迭代估计PD的坐标，由估计结果更新3D图
    def position_estimate(self):
        self.dc.update_figure()
        try:
            TN = ToastNotifier()
            TN.show_toast("提示", "开始调用MATLAB估计PD坐标", icon_path='logo_48.ico', duration=5, threaded=True)
            self.Matlab_thread.MATLAB_id = 2
            self.Matlab_thread.start()
        except:
            try:
                TN = ToastNotifier()
                TN.show_toast("调用MATLAB错误", "调用MATLAB运行Newton_PDpos_for_Python.m失败 :(", icon_path='logo_48.ico',
                              duration=5, threaded=True)
            except:
                QMessageBox.critical(self, "MATLAB Error", "调用MATLAB运行Newton_PDpos_for_Python.m失败")
if __name__ == '__main__':
    # QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) # 解决不同显示器分辨率问题 10.26无效
    app = QApplication(sys.argv)
    # dpi = (app.screens()[0]).logicalDotsPerInch()
    # print('dpi=', dpi)
    myappid = 'mycompany.myproduct.subproduct.version'  # arbitrary string
    ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
    myWin = MyWindow()
    # myWin.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
    # myWin.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)
    myWin.setGeometry(QtCore.QRect(360, 90, 1200, 900))
    myWin.setWindowFlag(QtCore.Qt.FramelessWindowHint)
    # 设置任务栏图标
    myWin.setWindowIcon(QtGui.QIcon(":/1/my_ico/logo.ico"))
    myWin.show()
    # loading = LoadWindow()
    # loading.setGeometry(QtCore.QRect(360, 90, 1200, 900))
    # loading.show()
    myWin.setMouseTracking(True)  # 鼠标跟踪
    sys.exit(app.exec_())